﻿// <copyright>
// Copyright (c) 2008 - 2009, Microsoft, All Rights Reserved
// </copyright>
//
// Licensed under the Microsoft Public License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
//
// http://www.opensource.org/licenses/ms-pl.html

namespace LiveLabs.Logging.Logs
{
    using System;
    using System.Diagnostics;
    using System.Threading;
    using LiveLabs.Logging.Resources;

    /// <summary>
    /// Implementation of <see cref="Log"/> that wraps other <see cref="Log"/> implementations
    /// to make them thread safe.
    /// </summary>
    /// <remarks>
    /// LockingLog does not wait infinitely for the lock to become available.  It will wait for
    /// a user configurable time before giving up.  This will ensure that deadlocks do not occur
    /// becaues of poorly written <see cref="Log"/> implementations.
    /// </remarks>
    public class LockingLog : Log, IDisposable
    {
        private readonly TimeSpan maxWaitTimeForLock;

        private readonly object wrappedLock;

        private volatile bool disposed;

        private Log wrapped;

        /// <summary>
        /// Initializes a new instance of the LockingLog class.
        /// </summary>
        /// <param name="toWrap">The <see cref="Log"/> to wrap with locking</param>
        /// <exception cref="ArgumentNullException">If <paramref name="wrapped"/> is null</exception>
        /// <remarks>
        /// Defaults the maximum wait time for lock to 2 seconds.
        /// </remarks>
        public LockingLog(Log toWrap)
            : this(toWrap, TimeSpan.FromSeconds(2))
        {
        }

        /// <summary>
        /// Initializes a new instance of the LockingLog class.
        /// </summary>
        /// <param name="toWrap">The <see cref="Log"/> to wrap with locking</param>
        /// <param name="maxWaitTime">The maximum amount of time to wait for the lock before giving up.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="wrapped"/> is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// If <paramref name="maxWaitTime"/>is less than half a second or greater than 5 minutes.
        /// </exception>
        public LockingLog(Log toWrap, TimeSpan maxWaitTime)
        {
            if (toWrap == null)
            {
                throw new ArgumentNullException("toWrap");
            }

            this.maxWaitTimeForLock = maxWaitTime;
            this.wrappedLock = new object();
            this.disposed = false;
            this.wrapped = toWrap;
        }

        /// <summary>
        /// Writes the message to the wrapped <see cref="Log"/> inside of a lock.
        /// </summary>
        /// <param name="message">The message to write to the log.</param>
        /// <param name="level">The <see cref="LogLevel"/> of the message.</param>
        public override void WriteLine(string message, LogLevel level)
        {
            if (this.disposed)
            {
                throw new ObjectDisposedException(this.GetObjectName());
            }

            this.ExecuteInsideLock(() => this.wrapped.WriteLine(message, level));
        }

        /// <summary>
        /// Returns a string representation of this instance.
        /// </summary>
        /// <returns>a string representation of this instance</returns>
        public override string ToString()
        {
            return this.GetObjectName();
        }

        /// <summary>
        /// Disposes of any resources that we might be holding
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (this.disposed)
            {
                return;
            }

            if (disposing)
            {
                Action cleanup = () =>
                {
                    try
                    {
                        IDisposable disposable = this.wrapped as IDisposable;
                        if (disposable != null)
                        {
                            disposable.Dispose();
                        }

                        this.wrapped = null;
                    }
                    finally
                    {
                        // no matter how we leave, we are disposed.
                        this.disposed = true;
                    }
                };

                this.ExecuteInsideLock(cleanup);
            }
        }

        private string GetObjectName()
        {
            return this.GetType().FullName + '[' + this.wrapped.GetType().FullName + ']';
        }

        private void ExecuteInsideLock(Action action)
        {
            if (Monitor.TryEnter(this.wrappedLock, this.maxWaitTimeForLock))
            {
                try
                {
                    action();
                }
                finally
                {
                    Monitor.Exit(this.wrappedLock);
                }
            }
            else
            {
#if SILVERLIGHT
                Debug.WriteLine(Messages.LockingLogTimeout);
#else
                Trace.TraceWarning(Messages.LockingLogTimeout);
#endif
            }
        }
    }
}
